package entity_model;

import com.google.common.base.Joiner;
import utils.Base;
import utils.titles.Title;

import java.util.*;

import static entity_model.Relationship.Direction.BELONGS_TO;
import static entity_model.Relationship.Role.Cardinality;
import static entity_model.Relationship.Role.Cardinality.MANY;
import static entity_model.Relationship.Role.Cardinality.ONE;

/**
* Created by nuno-rafael-figueiredo on 08-05-2014.
*/
public class Relationship {
    private Entity pivot;
    private final Role leftRole;
    private Direction direction = BELONGS_TO;
    private final Role rightRole;

    public Relationship(Cardinality cardinality, Entity leftEntity, Entity rightEntity, Title leftRoleTitle) {
        this.leftRole = new Role(leftEntity, cardinality, rightEntity);
        this.rightRole = new Role(ONE, leftEntity, leftRoleTitle);
    }

    public Relationship(Cardinality cardinality, Entity leftEntity, Entity rightEntity) {
        this.leftRole = new Role(leftEntity, cardinality, rightEntity);
        this.rightRole = new Role(ONE, leftEntity, leftEntity.getTitle());
    }

    public Relationship(Entity pivot, Role leftRole, Role rightRole) {
        this.pivot = pivot;
        this.leftRole = leftRole;
        this.rightRole = rightRole;
    }

    public Related getRelated() {
        return new Related(false);
    }

    public Related getReversed() {
        return new Related(true);
    }

    public Role getLeftRole() {
        return leftRole;
    }

    public Role getRightRole() {
        return rightRole;
    }

    @Override
    public String toString() {
        return Joiner.on(" ").join(
                getLeftRole(),
                direction,
                getRightRole()
        );
    }

    public boolean is(Cardinality leftCardinality, Cardinality rightCardinality) {
        return leftRole.getCardinality() == leftCardinality && rightRole.getCardinality() == rightCardinality;
    }

    /**
     * Created by nuno-rafael-figueiredo on 08-05-2014.
     */
    public enum Direction {
        BELONGS_TO("belongsTo", "belongsToMany"),
        OWNS("hasOne", "hasMany");

        private final Map<Cardinality, String> methodCalls = new LinkedHashMap<>();

        Direction(String one, String many) {
            methodCalls.put(ONE, one);
            methodCalls.put(MANY, many);
        }

        public Direction getReversed() {
            return this == BELONGS_TO ? OWNS : BELONGS_TO;
        }

        public String getMethodCall(Cardinality cardinality) {
            return methodCalls.get(cardinality);
        }
    }

    /**
     * Created by nuno-rafael-figueiredo on 08-05-2014.
     */
    public static class Role extends Base {
        private Cardinality cardinality;
        private transient final Entity entity;

        public Role(Entity me, Cardinality cardinality, Entity entity) {
            this(cardinality, entity, createTitle(me, cardinality, entity));
        }

        public Role(Cardinality cardinality, Entity entity, Title title) {
            super(Title.LOWER_CASE, title);
            this.cardinality = cardinality;
            this.entity = entity;
        }

        @Override
        public String toString() {
            return cardinality + " " + super.toString();
        }

        private static Title createTitle(Entity me, Cardinality cardinality, Entity entity) {
            String[] myWords = me.getTitle().getSingularized().toSnakeCase().split("_");
            String[] theOtherWords = entity.getTitle().getSingularized().toSnakeCase().split("_");

            List<String> finalWords = new ArrayList<>(Arrays.asList(theOtherWords));
            for(int i = 0; i < myWords.length && i < theOtherWords.length; i++)
                if (!myWords[i].equals(theOtherWords[i]))
                    break;
                else if(finalWords.size() > 1)
                    finalWords.remove(0);
                else
                    break;

            Title title = Title.fromSnakeCase(Joiner.on("_").join(finalWords));

            return cardinality == Cardinality.ONE ? title : title.getPluralized();

        }

        public Cardinality getCardinality() {
            return cardinality;
        }

        public Entity getEntity() {
            return entity;
        }

        public void setCardinality(Cardinality cardinality) {
            if (this.cardinality == cardinality)
                return;
            this.cardinality = cardinality;
            if (cardinality == MANY) pluralize();
            else singularize();
        }

        /**
         * Created by nuno-rafael-figueiredo on 08-05-2014.
         */
        public static enum Cardinality {
            ONE,
            MANY
        }

    }

    public class Related {
        private boolean reversed;

        public Related(boolean reversed){
            this.reversed = reversed;
        }

        public Role getMyRole(){
            return reversed ? rightRole : leftRole;
        }

        public Cardinality getMyRoleCardinality() {
            return getMyRole().getCardinality();
        }

        public Title getMyRoleTitle(){
            return getMyRole().getTitle();
        }

        public Direction getDirection(){
            return is(MANY, MANY) ? BELONGS_TO :reversed ? direction.getReversed() : direction;
        }

        public Role getTheOthersRole(){
            return reversed ? leftRole : rightRole;
        }

        public Cardinality getTheOthersRoleCardinality(){
            return getTheOthersRole().getCardinality();
        }

        public Title getTheOthersRoleTitle(){
            return getTheOthersRole().getTitle();
        }

        public void setDirection(Direction direction) {
            entity_model.Relationship.this.direction = direction;
        }

        @Override
        public String toString() {
            return Joiner.on(" ").join(
                    getMyRoleCardinality(),
                    getMyRoleTitle(),
                    getDirection(),
                    getTheOthersRoleCardinality(),
                    getTheOthersRoleTitle()
            );
        }

        public Entity getPivot() {
            return pivot;
        }
    }
}